package dmwr.service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import dmwr.Dmwr;
import dmwr.bean.Entry;
import dmwr.bean.Player;
import dmwr.bean.Pool;
import dmwr.bean.Shot;
import dmwr.bean.User;
import dmwr.util.Operation;

public class ServiceImpl implements Service {

	private static final Logger log = Logger.getLogger(ServiceImpl.class
			.getName());

	@Override
	public Page<User> listUsers(final int pageNum, final int pageSize) {
		return Dmwr.transactor().execute(new Operation<Page<User>>() {

			@Override
			public Page<User> execute() throws Exception {
				List<User> list = Dmwr.userRepo().list(
						(pageNum - 1) * pageSize, pageSize + 1);
				return page(list, pageNum, pageSize);
			}
		});
	}

	@Override
	public User getUser(final String username) {
		return Dmwr.transactor().execute(new Operation<User>() {

			@Override
			public User execute() throws Exception {
				return Dmwr.userRepo().byUsername(username);
			}
		});
	}

	@Override
	public void saveUser(final User user) throws Duplicate {
		Duplicate e = Dmwr.transactor().execute(new Operation<Duplicate>() {

			@Override
			public Duplicate execute() throws Exception {
				if (user.getCreated() == null) {
					User existing = Dmwr.userRepo().byUsername(
							user.getUsername());
					if (existing != null) {
						return new Duplicate(existing);
					}
				}
				Dmwr.userRepo().save(user);
				return null;
			}
		});
		if (e != null) {
			throw e;
		}
	}

	@Override
	public void deleteUser(final String username) {
		Dmwr.transactor().execute(new Operation<Object>() {

			@Override
			public Object execute() throws Exception {
				Dmwr.userRepo().delete(username);
				return null;
			}
		});
	}

	@Override
	public void createUserIfFirst(final User user) {
		Dmwr.transactor().execute(new Operation<Object>() {

			@Override
			public Object execute() throws Exception {
				User firstUser = Dmwr.userRepo().firstCreated();
				if (firstUser == null) {
					Dmwr.userRepo().save(user);
				}
				return null;
			}
		});
	}

	@Override
	public Page<Pool> listPools(final int pageNum, final int pageSize) {
		return Dmwr.transactor().execute(new Operation<Page<Pool>>() {

			@Override
			public Page<Pool> execute() throws Exception {
				List<Pool> list = Dmwr.poolRepo().list(
						(pageNum - 1) * pageSize, pageSize + 1);
				return page(list, pageNum, pageSize);
			}
		});
	}

	@Override
	public Pool getPool(final Long id) {
		return Dmwr.transactor().execute(new Operation<Pool>() {

			@Override
			public Pool execute() throws Exception {
				return Dmwr.poolRepo().byId(id);
			}
		});
	}

	@Override
	public void savePool(final Pool pool) throws Duplicate {
		Duplicate e = Dmwr.transactor().execute(new Operation<Duplicate>() {

			@Override
			public Duplicate execute() throws Exception {
				if (pool.getId() == null) {
					Pool existing = Dmwr.poolRepo().byTitle(pool.getTitle());
					if (existing != null) {
						return new Duplicate(existing);
					}
				}
				Dmwr.poolRepo().save(pool);
				return null;
			}
		});
		if (e != null) {
			throw e;
		}
	}

	@Override
	public void deletePool(final Long id) {
		Dmwr.transactor().execute(new Operation<Object>() {

			@Override
			public Object execute() throws Exception {
				Dmwr.poolRepo().delete(id);
				return null;
			}
		});
	}

	@Override
	public Page<Entry> listEntries(final int pageNum, final int pageSize) {
		return Dmwr.transactor().execute(new Operation<Page<Entry>>() {

			@Override
			public Page<Entry> execute() throws Exception {
				List<Entry> list = Dmwr.entryRepo().list(
						(pageNum - 1) * pageSize, pageSize + 1);
				return page(list, pageNum, pageSize);
			}
		});
	}

	@Override
	public Entry getEntry(final Long id) {
		return Dmwr.transactor().execute(new Operation<Entry>() {

			@Override
			public Entry execute() throws Exception {
				return Dmwr.entryRepo().byId(id);
			}
		});
	}

	@Override
	public void saveEntry(final Entry entry) throws Duplicate {
		Duplicate e = Dmwr.transactor().execute(new Operation<Duplicate>() {

			@Override
			public Duplicate execute() throws Exception {
				if (entry.getId() == null) {
					Entry existing = Dmwr.entryRepo().firstByText(
							entry.getPoolId(), entry.getText1(),
							entry.getText2());
					if (existing != null) {
						return new Duplicate(existing);
					}
				}
				Dmwr.entryRepo().save(entry);
				return null;
			}
		});
		if (e != null) {
			throw e;
		}
	}

	@Override
	public void deleteEntry(final Long id) {
		Dmwr.transactor().execute(new Operation<Object>() {

			@Override
			public Object execute() throws Exception {
				Dmwr.entryRepo().delete(id);
				return null;
			}
		});
	}

	// @Override
	// public void addAllEntriesToPool(final String poolTitle) {
	// Dmwr.transactor().execute(new Operation<Object>() {
	//
	// @Override
	// public Object execute() throws Exception {
	// Pool pool = Dmwr.poolRepo().byTitle(poolTitle);
	// int offset = 0;
	// List<Entry> list;
	// while (!(list = Dmwr.entryRepo().list(offset, 100)).isEmpty()) {
	// for (Entry entry : list) {
	// entry.setPoolId(pool.getId());
	// Dmwr.entryRepo().save(entry);
	// }
	// offset += 100;
	// }
	// return null;
	// }
	// });
	// }

	@Override
	public Session getSession(final String username, final Long poolId,
			final long length) {
		return Dmwr.transactor().execute(new Operation<Session>() {

			@Override
			public Session execute() throws Exception {
				List<Entry> entries = new ArrayList<Entry>();
				List<Shot> shots = new ArrayList<Shot>();
				Player player = Dmwr.playerRepo().byUsernameAndPoolId(username,
						poolId);
				if (player == null) {
					player = new Player(null, username, poolId, 0L);
					Dmwr.playerRepo().save(player);
				}
				long position = player.getPosition();
				Shot latest = Dmwr.shotRepo().latest(player.getId(), poolId);
				// ultima entry testata
				Entry lastTestedEntry = latest != null ? Dmwr.entryRepo().byId(
						latest.getEntryId()) : null;
				Long entryCreated = lastTestedEntry != null ? lastTestedEntry
						.getCreated() : 0;
				while (entries.size() < length) {
					// test all'istante corrente
					List<Shot> currentShots = Dmwr.shotRepo().at(
							player.getId(), poolId, position);
					if (!currentShots.isEmpty()) {
						for (Iterator<Shot> it = currentShots.iterator(); it
								.hasNext() && entries.size() < length;) {
							Shot shot = it.next();
							// entry del test
							entries.add(Dmwr.entryRepo()
									.byId(shot.getEntryId()));
							shots.add(shot);
						}
						// istante successivo
						position++;
					} else {
						// entry successiva all'ultima aggiunta, o prima entry
						Entry entry = Dmwr.entryRepo().firstCreatedAfter(
								poolId, entryCreated);
						if (entry != null) {
							// solo entry, no test
							entries.add(entry);
							// per iterazione successiva
							entryCreated = entry.getCreated();
							// istante successivo
							position++;
						} else {
							// entry tutte testate, prossimo test
							Shot shot = Dmwr.shotRepo().firstAfter(
									player.getId(), poolId, position);
							if (shot != null) {
								entries.add(Dmwr.entryRepo().byId(
										shot.getEntryId()));
								shots.add(shot);
								// istante successivo
								position = shot.getPosition() + 1;
							} else {
								// trovato nulla
								break;
							}
						}
					}
				}
				Collections.sort(shots, new Comparator<Shot>() {

					@Override
					public int compare(Shot t1, Shot t2) {
						return t1.getCreated().compareTo(t2.getCreated());
					}
				});
				return new Session(player.getPosition(), entries, Dmwr
						.entryRepo().count(), shots, latest != null ? latest
						.getCreated() : -1, lastTestedEntry);
			}
		});
	}

	@Override
	public void saveSession(final String username, final Long poolId,
			final List<Answer> answers) {
		Dmwr.transactor().execute(new Operation<Object>() {

			@Override
			public Object execute() throws Exception {
				Player player = Dmwr.playerRepo().byUsernameAndPoolId(username,
						poolId);
				boolean previousShotCreated = false;
				List<Shot> currentShots = Collections.emptyList();
				for (Answer answer : answers) {
					Entry entry = null;
					Shot shot = null;
					// test all'istante corrente
					if (currentShots.isEmpty()) {
						currentShots = Dmwr.shotRepo().at(player.getId(),
								poolId, player.getPosition());
					}
					if (!currentShots.isEmpty()) {
						shot = currentShots.get(0);
						// entry del test
						entry = Dmwr.entryRepo().byId(shot.getEntryId());
					} else {
						// test corrispondente all'ultima entry testata
						Shot s = Dmwr.shotRepo().latest(player.getId(), poolId);
						// entry successiva all'ultima aggiunta, o prima entry
						entry = Dmwr.entryRepo().firstCreatedAfter(
								poolId,
								s != null ? Dmwr.entryRepo()
										.byId(s.getEntryId()).getCreated() : 0);
						if (entry == null) {
							// entry tutte testate, prossimo test
							shot = Dmwr.shotRepo().firstAfter(player.getId(),
									poolId, player.getPosition());
							if (shot != null) {
								entry = Dmwr.entryRepo()
										.byId(shot.getEntryId());
							} else {
								Entry answerEntry = Dmwr.entryRepo().byId(
										answer.getEntryId());
								// throw new RuntimeException(
								System.out
										.println("Unexpected answer (position = "
												+ answer.getPosition()
												+ ", entry = "
												+ answerEntry
												+ ")");
								break;
							}
						}
					}
					if (answer.getPosition() != player.getPosition()
							|| answer.getEntryId() != entry.getId()) {
						Entry answerEntry = Dmwr.entryRepo().byId(
								answer.getEntryId());
						// throw new RuntimeException(
						System.out
								.println("Answer mismatch: actual = (position = "
										+ answer.getPosition()
										+ ", entry = "
										+ answerEntry
										+ "), expected = (position = "
										+ player.getPosition()
										+ ", entry = "
										+ entry + ")");
						break;
					}
					long trend = shot != null ? shot.getTrend() : 3;
					log.info("trend = " + trend + " (A)");
					trend = answer.isOk() ? trend + 1 : Math.min(trend - 1, 2);
					trend = Math.min(Math.max(1, trend), 31);
					log.info("trend = " + trend + " (B)");
					long delta = Math.min(1 << trend, Dmwr.entryRepo().count());
					long next = player.getPosition() + delta;
					if (!currentShots.isEmpty()) {
						currentShots.remove(shot);
					}
					if (currentShots.isEmpty()) {
						player.setPosition((shot != null ? shot.getPosition()
								: player.getPosition()) + 1);
						Dmwr.playerRepo().save(player);
					}
					if (shot == null) {
						shot = new Shot(null, player.getId(),
								entry.getPoolId(), entry.getId(), next, trend,
								null);
						if (previousShotCreated) {
							Thread.sleep(2);
						}
						previousShotCreated = true;
					} else {
						shot.setPosition(next);
						shot.setTrend(trend);
						previousShotCreated = false;
					}
					Dmwr.shotRepo().save(shot);
				}
				return null;
			}
		});
	}

	@Override
	public void reset() {
		Dmwr.transactor().execute(new Operation<Object>() {

			@Override
			public Object execute() throws Exception {
				Dmwr.playerRepo().deleteAll();
				Dmwr.entryRepo().deleteAll();
				Dmwr.shotRepo().deleteAll();
				return null;
			}
		});
	}

	private static <T> Page<T> page(List<T> list, int pageNum, int pageSize) {
		Integer prevPageNum = pageNum > 1 ? pageNum - 1 : null;
		Integer nextPageNum = null;
		if (list.size() > pageSize) {
			nextPageNum = pageNum + 1;
			list.remove(pageSize);
		}
		return new Page<T>(list, prevPageNum, nextPageNum);
	}
}
